import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.Element;
import java.io.File;
import processing.opengl.*;

private long timeLastUpdate;
private int level;
private int score;
private GameCamera gameCamera;
private Overlay overlay;
private Terrain terrain;
private ArrayList<Zombie> zombies;
private ArrayList<Bullet> bullets;
private ArrayList<BloodSplatter> bloodSplatters;
private ArrayList<BloodSplatter> grenadeSplatters;
private ArrayList<Item> items;
private Player shooter;
private int zombieCounter;

private int levelCounter;
private int bulletAngle = 8;
private int bulletXZ = 25;
private int bulletY = 1000;
 
private int cameraAngle = 150;
private int cameraXZ = 500;
private int cameraY = 10000;
 
private int zombieAppearance = 200;

float randomSpread = .1;
 
private boolean paused;
private boolean start;
private boolean over;

private Menu menu;

private final int COLLISION_OFFSET = 2;

PFont font;

void setup ()
{
    size(800, 600, OPENGL);
    String[] gameDataFilePath = loadStrings("filepath.txt");
    
    
    gameCamera = new GameCamera();
    gameCamera.setLocation(new PVector(0,10,0));
    
    overlay = new Overlay(gameCamera);
    terrain = new Terrain();
    menu = new Menu();
    
    
    zombies = new ArrayList<Zombie>();
    bullets = new ArrayList<Bullet>();
    bloodSplatters = new ArrayList<BloodSplatter>();
    grenadeSplatters = new ArrayList<BloodSplatter>();
    items = new ArrayList<Item>();
    
    shooter = new Player(gameCamera.getLocation(), gameCamera);
    zombieCounter = 0;
    level = 1;
    levelCounter = (level+15)*ceil(level/2f);
    
    paused = false;
    start = true;
    over = false;
    
    font = createFont("Arial Bold",48);
}

void draw ()
{
  if (start)
  {
    cursor();
    if (menu.isDone())
    {
      start = false;
      timeLastUpdate = System.currentTimeMillis();
      noCursor();
    }
    menu.draw();
  }
  else if (over)
  {
    background(0);
    textFont(font,36);
    fill(255);
    text("GAME OVER", width/2 - 100, height/2);
    text("Score: " + score, width/2 - 73, height/2 + 40);
    text("Press r to reset", width/2 - 125, height/2 + 80);
  }
  else
  {
    updateAll();
    drawAll();  
  }
}

void updateAll()
{
  long currentTime = System.currentTimeMillis();
  long timeElapsed = currentTime-timeLastUpdate;
  if (timeElapsed > 5000*zombieCounter && zombieCounter < levelCounter && !paused)
    addNewZombie();
  else if (zombieCounter == levelCounter && zombies.size() == 0 && !paused)
  {
    level++;
    terrain.setLevel(level-1);
    levelCounter = (level+15)*ceil(level/2);
    timeLastUpdate = currentTime;
    zombieCounter = 0;
  }
  trackball();
  PVector adjustedLocation = terrain.update(gameCamera.getLocation().x, gameCamera.getLocation().z);
  gameCamera.setLocation(adjustedLocation);
  checkCollisions();
}

void drawAll()
{
  background(0);
  noStroke();
  
  gameCamera.apply();
  setLights();
  
  terrain.draw();
  setLights();
  drawSprites();
  
  overlay.draw();
  
  textFont(font,36);
  fill(255);
  text(frameRate,20,60);
  text("Health: " + int(shooter.getHealth()) + " Ammo: " + shooter.getAmmo(), 20, 100);
  text("Level: " + level + " Score: " + score, 20, 140);
}

void reset()
{
  zombies.clear();
  items.clear();
  bullets.clear();
  
  shooter.reset();
  menu.reset();
  
  score = 0;
  zombieCounter = 0;
  level = 1;
  levelCounter = (level+15)*ceil(level/2f);
  timeLastUpdate = System.currentTimeMillis();
  terrain.setLevel(level-1);
  
  start = true;
  over = false;
}

void setLights()
{
  lightFalloff(1, 0, 0);
  lightSpecular(102, 102, 102);
  spotLight(255, 255, 255, gameCamera.getLocation().x, gameCamera.getLocation().y, gameCamera.getLocation().z, 
  gameCamera.getDirection().x, gameCamera.getDirection().y, gameCamera.getDirection().z, PI/8, 1.5);
  ambientLight(102, 102, 102); 
}

void checkCollisions()
{
   checkBZHit();
   checkHZHit(); 
   checkHIHit();
}

void checkHIHit()
{
  for (Item item : items)
  {
    if (shooter.getLocation().dist(item.getLocation()) <= shooter.getSize() + item.getSize())
        item.use();
  }
}

void checkHZHit()
{
   for (Zombie zombie : zombies)
    { 
      PVector distAway = PVector.sub(shooter.getLocation(), zombie.getLocation());
      distAway.y = 0;
      if (distAway.mag() < 10)
       {
         if (zombie.canAttack())
         {
           shooter.takeDamage(zombie.getDamage());
           overlay.hitGraphicCount=1;
           if (shooter.getHealth() == 0)
             over = true;
         }
       }
    }
}

void checkBZHit()
{
 for (Zombie zombie : zombies)
  {
     for (Bullet bullet : bullets)
     {
       if (bullet.getLocation().dist(zombie.getLocation()) <= zombie.getSize() + bullet.getSize() + COLLISION_OFFSET)
       {
         zombie.takeDamage(bullet.getDamage());
         bullet.takeDamage(0);
       }
     }
     for (BloodSplatter grenade : grenadeSplatters)
     {
       for (PVector shard : grenade.locations)
       {
         if (shard.dist(zombie.getLocation()) <= zombie.getSize() + grenade.getSize() + COLLISION_OFFSET)
         {
           zombie.takeDamage(grenade.getDamage());
         }
       }
     }
  }   
}

void addNewZombie()
{
  Zombie newZombie = new Zombie(level, shooter, PVector.add(shooter.getLocation(), new PVector(random(-1*zombieAppearance, zombieAppearance),0, random(-1*zombieAppearance, zombieAppearance))), terrain, zombies);
  zombies.add(newZombie);
  zombieCounter++;
}

void drawSprites()
{
  shooter.draw();
  
   for (int i = 0; i < zombies.size(); i++)
  {
    Zombie sprite = zombies.get(i);
    sprite.setPause(paused);
    sprite.draw(); 
    if (sprite.getHealth() <= 0)
     {
      score++;
      i--;
      dropItem(sprite.getLocation());
      zombies.remove(sprite);
     }
  }
   for (int i = 0; i < bullets.size(); i++)
  {
    Bullet sprite = bullets.get(i);
    sprite.setPause(paused);
    sprite.draw(); 
    if (sprite.getHealth() <= 0)
     {
        bullets.remove(sprite);
        BloodSplatter newSplatter;
        
        if(sprite.getHealth()<-10000)
          newSplatter = new BloodSplatter(sprite.location, 1);
        else 
          newSplatter = new BloodSplatter(sprite.location, 2);
          
        if (shooter.currentWeapon == 5)
          grenadeSplatters.add(newSplatter);  
          
        bloodSplatters.add(newSplatter);
        i--;
      }
  }
  
  for(int i = 0; i < bloodSplatters.size(); i++)
  {
    BloodSplatter bloodSplatter = bloodSplatters.get(i);
    bloodSplatter.draw();
    if(bloodSplatter.health<=0)
    {
      bloodSplatters.remove(bloodSplatter);
      if (grenadeSplatters.contains(bloodSplatter))
        grenadeSplatters.remove(bloodSplatter);
      i--;
    }
  }
  
  for (int i = 0; i < items.size(); i++)
  {
    Item item = items.get(i);
    item.draw();
    if (item.getHealth() <= 0)
    {
     items.remove(item); 
     i--;
    }
  }
}

void dropItem(PVector location)
{
  float itemDrop = random(100);
  if (itemDrop < 10)
  {
    Ammo machineGun = new Ammo(location, shooter, terrain, 2);
    items.add(machineGun);
  }
  else if (itemDrop < 20)
  {
    Ammo shotGun = new Ammo(location, shooter, terrain, 3);
    items.add(shotGun);
  }
  else if (itemDrop < 30)
  {
    Ammo sniper = new Ammo(location, shooter, terrain, 4);
    items.add(sniper);
  }
  else if (itemDrop < 40)
  {
    Health health = new Health(location, shooter, terrain);
    items.add(health);
  }
}
void shotgunSpread(PVector origin, PVector direction)
{
    PVector scatterDirection = new PVector(direction.x, direction.y, direction.z);
    scatterDirection.x = scatterDirection.x + random(-randomSpread, randomSpread);
    scatterDirection.y = scatterDirection.y + random(-randomSpread,randomSpread);
    scatterDirection.z = scatterDirection.z + random(-randomSpread,randomSpread);
    scatterDirection.normalize();
    Bullet newBullet1 = new Bullet(origin, scatterDirection, shooter.getDamage(), terrain, false);
    bullets.add(newBullet1);
    
    scatterDirection = new PVector(direction.x, direction.y, direction.z);
    scatterDirection.x = scatterDirection.x + random(-randomSpread, randomSpread);
    scatterDirection.y = scatterDirection.y + random(-randomSpread,randomSpread);
    scatterDirection.z = scatterDirection.z + random(-randomSpread,randomSpread);
    scatterDirection.normalize();
    Bullet newBullet2 = new Bullet(origin, scatterDirection, shooter.getDamage(), terrain, false);
    bullets.add(newBullet2);
    
    scatterDirection = new PVector(direction.x, direction.y, direction.z);
    scatterDirection.x = scatterDirection.x + random(-randomSpread, randomSpread);
    scatterDirection.y = scatterDirection.y + random(-randomSpread,randomSpread);
    scatterDirection.z = scatterDirection.z + random(-randomSpread,randomSpread);
    scatterDirection.normalize();
    Bullet newBullet3 = new Bullet(origin, scatterDirection, shooter.getDamage(), terrain, false);
    bullets.add(newBullet3);
    
    scatterDirection = new PVector(direction.x, direction.y, direction.z);
    scatterDirection.x = scatterDirection.x + random(-randomSpread, randomSpread);
    scatterDirection.y = scatterDirection.y + random(-randomSpread,randomSpread);
    scatterDirection.z = scatterDirection.z + random(-randomSpread,randomSpread);
    scatterDirection.normalize();
    Bullet newBullet4 = new Bullet(origin, scatterDirection, shooter.getDamage(), terrain, false);
    bullets.add(newBullet4);
    
    scatterDirection = new PVector(direction.x, direction.y, direction.z);
    scatterDirection.x = scatterDirection.x + random(-randomSpread, randomSpread);
    scatterDirection.y = scatterDirection.y + random(-randomSpread,randomSpread);
    scatterDirection.z = scatterDirection.z + random(-randomSpread,randomSpread);
    scatterDirection.normalize();
    Bullet newBullet5 = new Bullet(origin, scatterDirection, shooter.getDamage(), terrain, false); 
    bullets.add(newBullet5);
}

void trackball()
{
  float xRotation = -1*(mouseX-width/2.0);
  float yRotation = -1*((mouseY-height/2.0)/(height/2.0)*(PI/2))/50;
  gameCamera.setDirection(shiftDirection(new PVector(xRotation, yRotation,0), gameCamera.getDirection(), cameraAngle, cameraXZ, cameraY, false));
}

PVector shiftDirection(PVector _shift, PVector _original, int angleSensitivity, int xzSensitivity, int ySensitivity, boolean bullet)
 {
    PVector newDirection = new PVector();
    float angle = correctAngle(_original.x, _original.z);
    if (abs(_shift.x) > width*0.125 || bullet)
      angle += _shift.x/angleSensitivity;
    
    if (angle < 0)
      angle +=360;
    else if (angle >= 360)
      angle -= 360;
      if(gameCamera.fov==PI/3)
      {
        newDirection.x = xzSensitivity*sin(radians(angle));
          newDirection.z = -xzSensitivity*cos(radians(angle));
        newDirection.y = _original.y + _shift.y*ySensitivity;
      } else 
      {
        newDirection.x = xzSensitivity*sin(radians(angle));
          newDirection.z = -xzSensitivity*cos(radians(angle));
        newDirection.y = _original.y + _shift.y*ySensitivity;
      }
    
   newDirection.normalize();  
   return newDirection;  
 }
 
float correctAngle(float xComp, float zComp)
{
  float newAngle = -degrees(atan(xComp/zComp));
  if (xComp > 0 && zComp > 0)
    newAngle = (90 + newAngle) + 90;
  else if (xComp < 0 && zComp > 0)
    newAngle = newAngle + 180;
  else if (xComp < 0 && zComp < 0)
    newAngle = (90+ newAngle) + 270;
  return newAngle;
}
  
void keyPressed ()
{      
    if (key == 'w') 
    { 
      gameCamera.shiftLocationRelative(new PVector(0,0,1));
    } 
    else if (key == 's') 
    { 
      gameCamera.shiftLocationRelative(new PVector(0,0,-1));
    } 
    else if (key == 'a') 
    { 
      gameCamera.shiftLocationRelative(new PVector(-1,0,0));
    } 
    else if (key == 'd') 
    { 
      gameCamera.shiftLocationRelative(new PVector(1,0,0));
    } 
    else if (key == 'p')
    {
      paused = !paused; 
    }
    else if(key =='e' && shooter.currentWeapon==4)
    {
      if(gameCamera.fov == PI/3)
       {
       gameCamera.fov = PI/12; 
       } 
      else 
       {
      gameCamera.fov = PI/3;
       } 
    }
    else if (key == '1')
    {
      shooter.changeWeapon(1);
     gameCamera.fov = PI/3; 
    }
    else if (key == '2')
    {
      shooter.changeWeapon(2);
      gameCamera.fov = PI/3;
    }
    else if (key == '3')
    {
      shooter.changeWeapon(3);
      gameCamera.fov = PI/3;
    } 
    else if(key =='4')
    {
      shooter.changeWeapon(4);
    }
    else if (key == '5')
    {
      shooter.changeWeapon(5);
    }
    else if (key == '\\')
    {
      score += levelCounter - zombieCounter + 1 + zombies.size();
      zombies.clear();
      
      zombieCounter = levelCounter;
    }
    else if (key == 'r')
    {
      reset();
    }
    else if (key == 'o')
    {
      over = true; 
    }
    else if (key == 'h')
    {
      shooter.heal(100);
    }
}

void mousePressed()
{
    if (shooter.canShoot() && !start)
    {
      PVector mousePlace = new PVector(-1*(mouseX-width/2.0), -1*(mouseY-height/2.0)/(height/2.0)*(PI/2)/50, 0);
      PVector direction = shiftDirection(mousePlace, gameCamera.getDirection(), bulletAngle, bulletXZ, bulletY, true);
      direction = PVector.add(direction, gameCamera.getLocation());
      PVector origin = gameCamera.getLocation();
      direction.sub(origin);
      direction.normalize();
      if(shooter.currentWeapon==3)
      { //shotgun
//        println("shotgun!");
//        println("direction: " + direction);
        shotgunSpread(origin, direction);
      } 
      else if (shooter.currentWeapon ==4) //sniper
      {
        Bullet newBullet = new Bullet(origin, direction, shooter.getDamage(), terrain, false);
        newBullet.BULLET_SPEED = newBullet.BULLET_SPEED*2;
        bullets.add(newBullet);
      }      
      else if (shooter.currentWeapon == 5)
      {
        Bullet newBullet = new Bullet(origin, direction, shooter.getDamage(), terrain, true);
        bullets.add(newBullet);
      }
      else 
      {
        Bullet newBullet = new Bullet(origin, direction, shooter.getDamage(), terrain, false);
        bullets.add(newBullet);
      }
    }
}
